'use client';

import {
  Icon,
  IconButton,
  Menu,
  MenuButton,
  MenuItem,
  MenuList,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalOverlay,
  Tag,
  Tooltip,
} from '@chakra-ui/react';
import { useAuth, useClerk } from '@clerk/nextjs';
import {
  DndContext,
  DragEndEvent,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
} from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
  SortableContext,
  arrayMove,
  rectSortingStrategy,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { useVirtualizer } from '@tanstack/react-virtual';
import clsx from 'clsx';
import { runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useInView } from 'react-intersection-observer';

import { useStores } from '@/app/(root)/AppProviders';
import ExpandableText from '@/components/ExpandableText';
import Button, { ButtonSize, ButtonVariant } from '@/components/button/Button';
import CloseButton from '@/components/button/CloseButton';
import SwitchButton from '@/components/button/SwitchButton';
import {
  GridListItemWrapper,
  GridListWrapper,
} from '@/components/grid/GridListUtils';
import ImageUploader from '@/components/image/ImageUploader';
import ImageWithFallback from '@/components/image/ImageWithFallback';
import RegeneratePlaylistModal from '@/components/modal/RegeneratePlaylistModal';
import PlaylistSongRow from '@/components/song/PlaylistSongRow';
import SpinnerSVG from '@/components/svg/SpinnerSVG';
import ArtistTag from '@/components/tag/ArtistTag';
import { TextInput } from '@/components/textarea/TextInput';
import TextareaV2 from '@/components/textarea/TextareaV2';
import TitleText from '@/components/title/TitleText';
import { toast } from '@/components/toast/Toast';
import { PlaylistActionsProvider } from '@/context/PlaylistActionsContext';
import { usePreviewContext } from '@/context/PreviewContext';
import useBreakpoint, { useBreakpointMd } from '@/hooks/useBreakpoint';
import useDisclosure from '@/hooks/useDisclosure';
import usePageViewLog from '@/hooks/usePageViewLog';
import {
  PauseIcon,
  PlayIcon,
  ShareArrowIcon,
  SparklesIcon,
  ThumbsDownIcon,
  ThumbsUpIcon,
  TrashIcon,
} from '@/icons';
import { MoreVerticalIcon } from '@/icons';
import { ContextType } from '@/logging/contextTypes';
import logWebUserEvent from '@/logging/logWebUserEvent';
import { Playlist, isTimedOut } from '@/state/clipStore';
import { PlayContext } from '@/state/queueStore';
import { ModelType } from '@/state/sessionStore';
import {
  LARGE_IMAGE,
  LIKED_SONGS_PLAYLIST_COVER_URL,
  MENUS_Z_INDEX,
  TAILWIND_XL_MIN_WIDTH,
} from '@/utils/constants';
import { sharePlaylist } from '@/utils/download';
import { PageEventType, eventLogger } from '@/utils/event-logger';
import { ActionName, EventNames } from '@/utils/event-names';
import {
  READONLY_PLAYLIST_IDS,
  canUserEditPlaylist,
  filterPlaylistToUserOwnedSongs,
} from '@/utils/playlistConditionUtils';
import { getClerkSignInRedirectProps, getCountString } from '@/utils/utils';

import EmptyPlaylist from './EmptyPlaylist';

const PlaylistPageClient = observer(({ playlist }: { playlist: Playlist }) => {
  const {
    playbar: playbarState,
    clips,
    library,
    session,
    queue: queueStore,
    playlist: playlistStore,
    menus,
    createV2,
    genForm,
  } = useStores();
  const { isOpen, onOpen, onClose } = useDisclosure();
  const router = useRouter();
  const params = useSearchParams();
  const pathname = usePathname();
  const isTablet = useBreakpointMd();
  const isXl = useBreakpoint(TAILWIND_XL_MIN_WIDTH, true);
  const isMobile = !isTablet;
  const canEdit = canUserEditPlaylist(playlist);
  const isReadOnly = READONLY_PLAYLIST_IDS.includes(playlist.id);
  const [isTrashed, setIsTrashed] = useState(playlist.is_trashed);
  const [pagesLoaded, setPagesLoaded] = useState<number>(1);
  const [playlistData, setPlaylistData] = useState<Playlist>(playlist);
  const [loadingSetMetadata, setLoadingSetMetadata] = useState(false);
  const [metadataError, setMetadataError] = useState<string | null>(null);
  const [playlistReaction, setPlaylistReaction] = useState<string | null>(
    !!playlist.reaction
      ? { L: 'LIKE', D: 'DISLIKE' }[playlist.reaction] || null
      : null
  );
  const [name, setName] = useState(playlist.name);
  const [description, setDescription] = useState(playlist.description);
  const [nextOffset, setNextOffset] = useState<number | null>(null);
  const [upvoteCount, setUpvoteCount] = useState(playlist.upvote_count || 0);

  // Image upload in playlist metadata modal
  const [initialImageURL, setInitialImageURL] = useState<string | null>(
    playlist.image_url || null
  );
  const [isGenerating, setIsGenerating] = useState(false);
  const [editedImagePrompt, setEditedImagePrompt] = useState<string>('');
  const [playlistImageDataURL, setPlaylistImageDataURL] = useState<
    string | null
  >(null);
  const [shouldInfiniteLoad, setShouldInfiniteLoad] = useState(true);

  const NAME_MAX_LENGTH = 80;
  const DESCRIPTION_MAX_LENGTH = 200;
  const playlistClips = clips.getPlaylistClips(playlist.id);
  const [loadingInitialClips, setLoadingInitialClips] = useState<boolean>(
    playlistClips === undefined ||
      (playlistClips.length === 0 && playlist.num_total_results > 0)
  );
  const [loadingMoreClips, setLoadingMoreClips] = useState<boolean>(false);

  const { setAllowFlushToTop } = usePreviewContext();

  const parentRef = useRef<any>(undefined);

  const {
    isOpen: isOpenRegeneratePlaylist,
    onOpen: onOpenRegeneratePlaylist,
    onClose: onCloseRegeneratePlaylist,
  } = useDisclosure();

  const { ref: spinnerRef } = useInView({
    threshold: 0,
    onChange: async (inView) => {
      if (inView) {
        setLoadingMoreClips(true);
        const offset = rowVirtualizer.scrollOffset || 0;
        const { loadedClips, shouldLoadMoreClips } = await clips.loadPlaylist(
          playlist.id,
          pagesLoaded + 1
        );
        if (loadedClips) {
          setIndexToClip({ ...indexToClip, ...loadedClips });
          setClipIndexes([...clipIndexes, ...Object.keys(loadedClips)]);
          setPagesLoaded(pagesLoaded + 1);

          if (session.flags?.['playlist-virtual']) {
            setNextOffset(offset);
          }
        }
        if (typeof shouldLoadMoreClips === 'boolean' && !shouldLoadMoreClips) {
          setShouldInfiniteLoad(false);
        }
        setLoadingMoreClips(false);
      }
    },
  });

  useEffect(() => {
    if (
      params.get('new') !== null &&
      !isOpen &&
      playlist.is_owned &&
      !isReadOnly
    ) {
      onOpen();
    }
    if (params.get('campaign')) {
      eventLogger.logWebPageEvent({
        userId: session.userId,
        eventType: PageEventType.REDIRECT,
        element: params.get('campaign'),
        entityType: 'campaign',
      });
    }
  }, [params]);

  usePageViewLog({
    actionName: 'PageViewed',
    componentContext: 'playlist',
    principalObjectType: 'playlist',
    principalObjectValue: playlist.id,
  });

  useEffect(() => {
    const loadData = async () => {
      if (loadingInitialClips) {
        const { loadedClips, shouldLoadMoreClips } = await clips.loadPlaylist(
          playlist.id,
          1
        );
        if (loadedClips) {
          setLoadingInitialClips(false);
          setIndexToClip({ ...loadedClips });
          setClipIndexes([...Object.keys(loadedClips)]);
          setPagesLoaded(1);
        }
        if (typeof shouldLoadMoreClips === 'boolean' && !shouldLoadMoreClips) {
          setShouldInfiniteLoad(false);
        }
      } else {
        if (!clips.playlistById[playlist.id]) {
          clips.playlistById[playlist.id] = {
            ...playlist,
            clipIds: playlist.playlist_clips
              .filter((pClip: any) => !isTimedOut(pClip.clip))
              .map((clip: any) => clip.id),
          };
        }
        clips.updateClips(playlist.playlist_clips.map((c) => c.clip));
      }

      if (!playbarState.clip) {
        queueStore.setPlayContext({
          clips: playlist.playlist_clips
            .filter((pClip: any) => !isTimedOut(pClip.clip))
            .map((playlistClip: any) => playlistClip.clip),
          contextId: playlist.id,
          contextType: ContextType.Playlist,
        });
      }
      setAllowFlushToTop(true);
      return () => {
        setAllowFlushToTop(false);
      };
    };
    loadData();
  }, []);

  useEffect(() => {
    if (!playlistClips || loadingMoreClips) return;

    const newPlaylistClips = playlistClips.map((playlistClip: any) => ({
      ...playlistClip,
      ...(clips.clipById[playlistClip.clip.id]
        ? { clip: clips.clipById[playlistClip.clip.id] }
        : {}),
    }));

    const newIndexToClip = {
      ...Object.fromEntries(
        newPlaylistClips
          .filter((pClip: any) => !isTimedOut(pClip.clip))
          .map((clip: any, index: number) => [index, clip])
      ),
    };
    const newClipIndexes = Object.keys(newIndexToClip);
    setPlaylistData({
      ...playlistData,
      playlist_clips: newPlaylistClips,
    });
    setIndexToClip(newIndexToClip);
    setClipIndexes(newClipIndexes);

    // if a clip is added to a currently playing playlist,
    // let the play queue know about it.
    if (queueStore.isPlaylistCurrentContext(playlist.id)) {
      queueStore.setClips(
        newClipIndexes.map(
          (clipIndex: string) => newIndexToClip[clipIndex].clip
        )
      );
    }
  }, [playlistClips, clips.clipById]);

  const descriptionLength = description?.length || 0;
  const descriptionOverLength = descriptionLength > DESCRIPTION_MAX_LENGTH;
  const nameOverLength = (name?.length || 0) > NAME_MAX_LENGTH;
  const isSaveDisabled = descriptionOverLength || nameOverLength || !name;

  const filteredClips = playlist.playlist_clips.filter((clip: any) => {
    return !isTimedOut(clip.clip);
  });
  const filteredClipMap = filteredClips.map((clip: any, index: number) => [
    index,
    clip,
  ]);

  const [indexToClip, setIndexToClip] = useState(
    Object.fromEntries(filteredClipMap)
  );

  const [clipIndexes, setClipIndexes] = useState(Object.keys(indexToClip));

  const clipIndexObjs = clipIndexes.map((clipIndex, playlistIndex) => ({
    id: clipIndex,
    playlistIndex,
    clip: indexToClip[clipIndex],
  }));

  const estimateSize = useCallback(() => {
    return isXl ? 85 : 122;
  }, [isXl]);

  const rowVirtualizer = useVirtualizer({
    count: clipIndexObjs.length,
    getScrollElement: () => parentRef.current,
    estimateSize: estimateSize,
    overscan: 10, // overscan for drag-and-drop
  });
  const virtualRows = rowVirtualizer.getVirtualItems();
  const minVirtualIndex = virtualRows[0]?.index || 0;

  useEffect(() => {
    const handleResize = () => {
      rowVirtualizer.measure();
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [rowVirtualizer]);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: {
        distance: 10,
      },
    })
  );

  const getActionName = (
    currentValue: string | null,
    newValue?: string | null
  ) => {
    if (newValue === 'LIKE') return ActionName.likePlaylist;
    else if (newValue === 'DISLIKE') return ActionName.dislikePlaylist;
    else if (currentValue === 'LIKE' && newValue === null)
      return ActionName.undoLikePlaylist;
    else if (currentValue === 'DISLIKE' && newValue === null)
      return ActionName.undoDislikePlaylist;
  };

  // Helper to update playlist context while preserving manual queue state
  const updatePlaylistContext = useCallback(
    (playContext: PlayContext) => {
      const savedActiveQueue = queueStore.activeQueue;
      const savedContextQueueIndex = queueStore.contextQueueIndex;
      const savedCurrentManualClip = queueStore.currentManualClip;
      queueStore.setPlayContext(playContext);

      // Wrap manual queue state restoration in runInAction for proper MobX tracking
      if (savedActiveQueue === 'manual') {
        runInAction(() => {
          queueStore.activeQueue = savedActiveQueue;
          queueStore.contextQueueIndex = savedContextQueueIndex;
          queueStore.currentManualClip = savedCurrentManualClip;
        });
      }
    },
    [queueStore]
  );

  const handleDragEnd = async (event: DragEndEvent) => {
    if (!canEdit) {
      return;
    }

    const { active, over } = event;

    if (over && active.id !== over.id) {
      const oldIndex = clipIndexes.indexOf(String(active.id));
      const newIndex = clipIndexes.indexOf(String(over.id));
      const newClipIndexes = arrayMove(clipIndexes, oldIndex, newIndex);

      // Get current index from activeQueue (only applies to context queue)
      const currentIndex =
        queueStore.activeQueue === 'context'
          ? queueStore.contextQueueIndex
          : null;

      setClipIndexes(newClipIndexes);

      const newCurrentIndex = {
        currentIndex,
      };

      if (currentIndex !== null) {
        if (oldIndex === currentIndex) {
          newCurrentIndex.currentIndex = newIndex;
        } else if (oldIndex > currentIndex && newIndex <= currentIndex) {
          newCurrentIndex.currentIndex = currentIndex + 1;
        } else if (oldIndex < currentIndex && newIndex >= currentIndex) {
          newCurrentIndex.currentIndex = currentIndex - 1;
        }
      }

      if (
        queueStore.isPlaylistCurrentContext(playlist.id) &&
        playbarState.clip
      ) {
        updatePlaylistContext({
          currentIndex: newCurrentIndex.currentIndex || 0,
          clips: newClipIndexes.map(
            (clipIndex: string) => indexToClip[clipIndex].clip
          ),
          contextId: playlist.id,
          contextType: ContextType.Playlist,
        });
      }

      const { response } = await library.apiClient.POST(
        '/api/playlist/update_clips/',
        {
          body: {
            playlist_id: playlist.id,
            update_type: 'reorder',
            metadata: {
              from_index: oldIndex,
              to_index: newIndex,
            },
          },
        }
      );
      return response;
    }
  };

  const handleTogglePublic = async () => {
    const isPublicState = playlistData.is_public;
    setPlaylistData({ ...playlistData, is_public: !isPublicState });
    try {
      await playlistStore.setPlaylistVisibility(playlist.id, !isPublicState);
      toast({
        title: `Playlist is now ${!isPublicState ? 'public' : 'link only'}.`,
        duration: 2000,
        isClosable: true,
      });
    } catch (error) {
      console.error('Failed to update playlist visibility:', error);
      setPlaylistData({ ...playlistData, is_public: isPublicState });
    }
  };

  const { isSignedIn } = useAuth();
  const clerk = useClerk();

  const handlePlaylistReaction = async (
    reactionRequest: 'LIKE' | 'DISLIKE'
  ) => {
    if (!isSignedIn) {
      clerk.openSignIn({
        withSignUp: true,
        ...getClerkSignInRedirectProps(`/playlist/${playlist.id}`),
      });
    } else {
      const currentPlaylistReaction = playlistReaction;
      const newPlaylistReaction =
        currentPlaylistReaction === reactionRequest ? null : reactionRequest;
      setPlaylistReaction(newPlaylistReaction);
      const upvoteCountChange =
        newPlaylistReaction === 'LIKE'
          ? 1
          : currentPlaylistReaction === 'LIKE'
            ? -1
            : 0;
      setUpvoteCount(upvoteCount + upvoteCountChange);
      eventLogger.segmentTrack(EventNames.playlistActionEvent, {
        isMobile,
        playlistId: playlist.id,
        userId: session?.userId,
        actionName: getActionName(currentPlaylistReaction, newPlaylistReaction),
        isUserPlaylistOwner: false,
        clickSourceUrl: pathname,
      });

      const { response } = await library.apiClient.POST(
        '/api/playlist_reaction/{playlist_id}/update_reaction_type/',
        {
          params: {
            path: {
              playlist_id: playlist.id,
            },
          },
          body: {
            reaction: newPlaylistReaction,
          },
        }
      );
      if (!response.ok) {
        setPlaylistReaction(currentPlaylistReaction);
        setUpvoteCount(upvoteCount - upvoteCountChange);
      }
    }
  };

  const trashPlaylist = async (
    playlistId: string,
    undoTrash: boolean = false
  ) => {
    await library.apiClient.POST('/api/playlist/trash/', {
      body: {
        playlist_id: playlistId,
        undo_trash: undoTrash,
      },
    });
  };

  const handlePlaylistCondition = async () => {
    const filteredPlaylist = await filterPlaylistToUserOwnedSongs(
      playlist,
      clips,
      session
    );

    if (!filteredPlaylist) {
      toast({
        title: 'No original songs found',
        description:
          'Please choose a playlist with at least one song you made.',
        status: 'warning',
        duration: 3000,
        isClosable: true,
      });
      return;
    }

    createV2.setConditioningPlaylist(filteredPlaylist);
    createV2.resetUnderpainting();
    createV2.resetOverpainting();
    genForm.setTask('playlist_condition');
    createV2.setIsAdvancedMode(true);

    logWebUserEvent({
      actionName: 'InspirePlaylistPageClicked',
      context: {
        playlistId: playlist?.id,
        isOwner: playlist?.is_owned,
      },
    });

    // Set model only if not already bluejay, and only if available
    const canUseBluejay = session
      .getViewableModels()
      .find((m) => m.external_key === 'chirp-bluejay')?.can_use;
    if (canUseBluejay) {
      genForm.setMv('chirp-bluejay');
    }

    router.push('/create');
  };

  useEffect(() => {
    if (session.flags?.['playlist-virtual']) {
      return;
    }
    if (nextOffset !== null && rowVirtualizer.scrollOffset !== nextOffset) {
      rowVirtualizer.scrollToOffset(nextOffset);
    } else if (rowVirtualizer.scrollOffset === nextOffset) {
      setNextOffset(null);
    }
  }, [clipIndexObjs, rowVirtualizer.scrollOffset, nextOffset]);

  const onRemoveFromPlaylist = async () => {
    if (!canEdit) {
      return;
    }
    // DATA FLOW: menus.selectedClipIds is populated synchronously in afterSelectionChange callback below
    // This avoids race condition where menus.selected (from MobX store) updates asynchronously
    // and could be stale by the time this deletion handler executes.
    //
    // Flow: User selects → GridListWrapper.onSelectionChange → afterSelectionChange(selection) →
    //       menus.setPlaylistSelectedClipIds (sync) → onRemoveFromPlaylist reads menus.selectedClipIds
    const selectedClipIds = menus.selectedClipIds || [];

    // Convert clip IDs to playlist indexes for the backend API
    const selectedArray = selectedClipIds
      .map((clipId) => {
        // Find the index in clipIndexes that corresponds to this clipId
        return clipIndexes.find((idx) => indexToClip[idx]?.clip?.id === clipId);
      })
      .filter((idx): idx is string => idx !== undefined);

    const selectedPlaylistIndexes = selectedArray.map((sIndex) => {
      const playlistIndex = clipIndexes.findIndex(
        (clipInd) => clipInd === sIndex
      );
      return playlistIndex;
    });

    const { response } = await library.apiClient.POST(
      '/api/playlist/update_clips/',
      {
        body: {
          playlist_id: playlist.id,
          update_type: 'remove',
          metadata: {
            indexes: selectedPlaylistIndexes,
          },
        },
      }
    );

    if (!response.ok) return;

    const newClipIndexes = clipIndexes.filter(
      (cIndex) => !selectedArray.includes(cIndex)
    );
    setClipIndexes(newClipIndexes);

    const currentIndexBeforeDeletion =
      queueStore.activeQueue === 'context'
        ? queueStore.contextQueueIndex
        : null;

    // Calculate how many songs were removed before the current song
    const numRemovedBeforeCurrent = selectedPlaylistIndexes.filter(
      (sIndex) => sIndex < (currentIndexBeforeDeletion || 0)
    ).length;

    // Adjust the current song's index by subtracting removed songs
    const currentIndexAfterDeletion =
      numRemovedBeforeCurrent > 0
        ? (currentIndexBeforeDeletion || 0) - numRemovedBeforeCurrent
        : currentIndexBeforeDeletion;
    clips.playlistById[playlist.id].num_total_results =
      clips.playlistById[playlist.id].num_total_results - selectedArray.length;

    if (queueStore.isPlaylistCurrentContext(playlist.id)) {
      updatePlaylistContext({
        currentIndex: currentIndexAfterDeletion || 0,
        currentPlayingSongIsRemoved: selectedPlaylistIndexes.includes(
          currentIndexBeforeDeletion === null ? -1 : currentIndexBeforeDeletion
        ),
        clips: newClipIndexes.map(
          (clipIndex: string) => indexToClip[clipIndex].clip
        ),
        contextId: playlist.id,
        contextType: ContextType.Playlist,
      });
    }
  };

  return (
    <main className='flex max-w-full flex-1 flex-col'>
      <div
        className='mt-0 flex max-w-full flex-1 overflow-y-auto'
        style={{
          scrollbarWidth: 'none',
        }}
      >
        <div className='-mt-2 flex max-w-full flex-1 flex-col md:my-6'>
          <div className='flex flex-col'>
            <div className='flex max-w-full flex-col items-center p-0 py-4 pl-0 md:flex-row md:items-end md:p-6 md:py-4 md:pl-4'>
              <ImageWithFallback
                imageSize={LARGE_IMAGE}
                src={
                  playlistData.id === 'liked'
                    ? LIKED_SONGS_PLAYLIST_COVER_URL
                    : playlistData.image_url ||
                      playlistData.playlist_clips[0]?.clip?.image_url ||
                      null
                }
                alt='Playlist cover art'
                onClick={() => {
                  if (canEdit) onOpen();
                }}
                className={clsx(
                  'w-[400px] self-start rounded-md object-cover sm:w-[180px]',
                  {
                    'cursor-pointer': canEdit,
                    'cursor-default': !canEdit,
                  }
                )}
              />
              <div
                className='flex max-w-full flex-1 flex-col justify-end gap-2 px-0 py-4 md:px-0 md:py-0 md:pl-8'
                style={{ width: isMobile ? 'calc(100% - 16px)' : undefined }}
              >
                {isTrashed && (
                  <Tag colorScheme='red' size='md' mr={4} w='fit-content'>
                    <Icon as={TrashIcon} />
                    <span className='ml-1 font-sans'>In Trash</span>
                  </Tag>
                )}

                <TitleText
                  text={(playlistData || playlist).name || 'Untitled Playlist'}
                />
                <div className='w-full pr-4 text-left text-foreground-primary'>
                  {canEdit ? (
                    <button
                      onClick={() => {
                        onOpen();
                      }}
                      className='w-full text-left'
                    >
                      <span className='line-clamp-3 text-left font-sans'>
                        {(playlistData || playlist).description ||
                          'Add playlist description'}
                      </span>
                    </button>
                  ) : (
                    <ExpandableText
                      text={playlist.description || ''}
                      lineClamp={3}
                    />
                  )}
                </div>
                <div className='flex flex-row items-center gap-2'>
                  {!isReadOnly && !!playlist.user_handle && (
                    <>
                      <ArtistTag
                        displayName={
                          playlist.user_display_name || playlist.user_handle
                        }
                        handle={playlist.user_handle}
                        imageUrl={playlist.user_avatar_image_url || undefined}
                      />
                      {'·'}
                    </>
                  )}
                  <div className='line-clamp-1 w-fit overflow-hidden text-ellipsis'>
                    {`${playlist.num_total_results || 0} song${
                      playlist.num_total_results === 1 ? '' : 's'
                    }`}
                  </div>
                </div>
              </div>
              <div
                className='flex flex-row items-baseline justify-center gap-4 pr-0 md:flex-col md:items-end md:justify-end md:gap-0 md:pr-8'
                style={{ width: isMobile ? 'calc(100% - 16px)' : undefined }}
              >
                <div className='flex flex-row items-center justify-end gap-2'>
                  {canEdit && (
                    <SwitchButton
                      buttonText='Public'
                      onChange={handleTogglePublic}
                      checked={playlistData.is_public}
                    />
                  )}
                  {session.flags?.['playlist-condition'] &&
                    session.billingModels?.find((model: ModelType) => {
                      return model.external_key === 'chirp-bluejay';
                    })?.can_use &&
                    (playlist.is_owned || playlist.is_public) && (
                      <Button
                        variant={ButtonVariant.Standard}
                        onClick={handlePlaylistCondition}
                        iconStart={SparklesIcon}
                      >
                        Inspire
                      </Button>
                    )}
                  <div className='flex w-full flex-1 flex-row justify-end gap-2 lg:w-auto'>
                    <Button
                      variant={ButtonVariant.Standard}
                      size={ButtonSize.Small}
                      icon={
                        queueStore.isPlaylistCurrentContext(playlist.id) &&
                        playbarState.isPlaying ? (
                          <PauseIcon className='mx-1 h-3 w-3' />
                        ) : (
                          <PlayIcon className='mx-1 h-3 w-3' />
                        )
                      }
                      onClick={() => {
                        if ((playlist.playlist_clips || []).length > 0) {
                          if (
                            queueStore.isPlaylistCurrentContext(playlist.id)
                          ) {
                            playbarState.togglePlay();
                            if (playbarState.isPlaying) {
                              playlistStore.incrementPlaylistPlayCount(
                                playlist.id,
                                playlist.playlist_clips[0].clip.id
                              );
                            }
                            return;
                          }
                          queueStore.setPlayContext({
                            contextType: ContextType.Playlist,
                            contextId: playlist.id,
                            clips: playlist.playlist_clips.map(
                              (pc: any) => pc.clip
                            ),
                            currentIndex: 0,
                          });
                          playbarState.playClip(
                            playlist.playlist_clips[0].clip
                          );
                          playlistStore.incrementPlaylistPlayCount(
                            playlist.id,
                            playlist.playlist_clips[0].clip.id
                          );
                        }
                      }}
                    />
                    <Button
                      variant={ButtonVariant.Standard}
                      size={ButtonSize.Small}
                      icon={ShareArrowIcon}
                      onClick={() => {
                        sharePlaylist(playlist.id);
                        eventLogger.segmentTrack(
                          EventNames.playlistActionEvent,
                          {
                            isMobile,
                            playlistId: playlist.id,
                            userId: session?.userId,
                            actionName: ActionName.sharePlaylistWithLink,
                            isUserPlaylistOwner: false,
                            clickSourceUrl: pathname,
                          }
                        );
                      }}
                    />
                  </div>
                  {!isReadOnly && (
                    <>
                      {!playlist.is_owned && (
                        <>
                          <button
                            onClick={async () => {
                              handlePlaylistReaction('LIKE');
                            }}
                            className={`mx-2 flex cursor-pointer flex-row items-center justify-center gap-2 rounded-full font-sans text-sm`}
                          >
                            <ThumbsUpIcon
                              className={clsx({
                                'h-4 w-4 hover:brightness-125': true,
                                'fill-foreground-inactive':
                                  playlistReaction !== 'LIKE',
                                'fill-foreground-primary':
                                  playlistReaction === 'LIKE',
                              })}
                            />
                            <div
                              className={clsx({
                                'text-foreground-inactive':
                                  playlistReaction !== 'LIKE',
                                'text-foreground-primary':
                                  playlistReaction === 'LIKE',
                              })}
                            >
                              {upvoteCount && upvoteCount > 0
                                ? getCountString(upvoteCount)
                                : null}
                            </div>
                          </button>
                          <button
                            onClick={async () => {
                              handlePlaylistReaction('DISLIKE');
                            }}
                            className={`mx-2 flex cursor-pointer flex-row items-center justify-center gap-2 rounded-full font-sans text-sm`}
                          >
                            <ThumbsDownIcon
                              className={clsx({
                                'h-4 w-4 hover:brightness-125': true,
                                'fill-foreground-inactive':
                                  playlistReaction !== 'DISLIKE',
                                'fill-foreground-primary':
                                  playlistReaction === 'DISLIKE',
                              })}
                            />
                          </button>
                        </>
                      )}
                    </>
                  )}
                  {canEdit && (
                    <Menu>
                      <MenuButton
                        size={'sm'}
                        aria-label='More Actions'
                        as={IconButton}
                        icon={
                          <MoreVerticalIcon className='size-4 text-foreground-primary' />
                        }
                        variant='ghost'
                      />
                      <MenuList
                        zIndex={MENUS_Z_INDEX}
                        className='bg-background-secondary!'
                      >
                        <MenuItem
                          className='hover:bg-background-tertiary'
                          backgroundColor='transparent'
                          fontFamily={'Neue Montreal'}
                          onClick={onOpen}
                        >
                          Edit Details
                        </MenuItem>
                        {process.env.NEXT_PUBLIC_ML_DEV_MODE === 'true' && (
                          <Tooltip
                            label='Unavailable if playlist has over 50 songs'
                            isDisabled={playlist.num_total_results <= 50}
                          >
                            <MenuItem
                              bg={'transparent'}
                              fontFamily={'Neue Montreal'}
                              onClick={() => {
                                if (playlist.num_total_results <= 50) {
                                  library.setActivePlaylist(playlist);
                                  onOpenRegeneratePlaylist();
                                }
                              }}
                              isDisabled={playlist.num_total_results > 50}
                            >
                              Regenerate Playlist
                            </MenuItem>
                          </Tooltip>
                        )}
                        {isTrashed ? (
                          <MenuItem
                            bg={'transparent'}
                            fontFamily={'Neue Montreal'}
                            _hover={{
                              background: 'rgba(255, 255, 255, 0.1)',
                            }}
                            onClick={async () => {
                              await trashPlaylist(playlist.id, true);
                              toast({
                                title: 'Playlist removed from trash',
                                status: 'info',
                                duration: 3000,
                                isClosable: true,
                              });
                              setIsTrashed(false);
                            }}
                          >
                            Restore to Library
                          </MenuItem>
                        ) : (
                          <MenuItem
                            className='hover:bg-background-tertiary'
                            bg={'transparent'}
                            onClick={async () => {
                              await trashPlaylist(playlist.id);
                              toast({
                                title: 'Playlist moved to trash',
                                status: 'info',
                                duration: 3000,
                                isClosable: true,
                              });
                              setIsTrashed(true);
                            }}
                          >
                            Move to Trash
                          </MenuItem>
                        )}
                      </MenuList>
                    </Menu>
                  )}
                </div>
              </div>
            </div>
          </div>
          <div
            ref={parentRef}
            className='flex flex-1 flex-col p-6 py-4 pl-2 md:overflow-y-auto md:py-4 md:pl-2'
            style={{
              paddingBottom: !!playbarState.clip ? '108px' : undefined,
            }}
          >
            {loadingInitialClips ? (
              <div className='flex h-full w-full items-center justify-center'>
                <SpinnerSVG />
              </div>
            ) : (
              <PlaylistActionsProvider
                onRemoveFromPlaylist={
                  canEdit ? onRemoveFromPlaylist : undefined
                }
              >
                <DndContext
                  sensors={sensors}
                  collisionDetection={closestCenter}
                  onDragEnd={handleDragEnd}
                  modifiers={[restrictToVerticalAxis]}
                >
                  <SortableContext
                    items={clipIndexes}
                    strategy={
                      session.flags?.['playlist-virtual']
                        ? rectSortingStrategy
                        : verticalListSortingStrategy
                    }
                  >
                    {clipIndexes.length === 0 && playlist && (
                      <EmptyPlaylist
                        isOwned={!!playlist.is_owned}
                        userHandle={playlist.user_handle || ''}
                        userDisplayName={playlist.user_display_name || ''}
                      />
                    )}
                    <GridListWrapper
                      style={
                        session.flags?.['playlist-virtual']
                          ? {
                              height: `${rowVirtualizer.getTotalSize()}px`,
                              width: '100%',
                              position: 'relative',
                            }
                          : undefined
                      }
                      aria-label={`${(clipIndexObjs || []).length} Clips`}
                      items={
                        session.flags?.['playlist-virtual']
                          ? [
                              ...virtualRows.map(
                                (virtualRow) => clipIndexObjs[virtualRow.index]
                              ),
                              ...(shouldInfiniteLoad
                                ? [
                                    {
                                      isLoadingIndicator: true,
                                      id: '-1',
                                      playlistIndex: -1,
                                      clip: null,
                                    },
                                  ]
                                : []),
                            ]
                          : clipIndexObjs || []
                      }
                      afterSelectionChange={({ setPreviewClip, selection }) => {
                        // DATA FLOW: This callback receives synchronous selection state from GridListWrapper
                        // We immediately extract clip IDs and store them in menus.selectedClipIds
                        // This ensures onRemoveFromPlaylist (above) always has fresh selection data
                        //
                        // The 'selection' parameter comes directly from React Aria's onSelectionChange,
                        // avoiding the async delay of MobX's menus.selected state propagation
                        const selectedIds =
                          selection === 'all'
                            ? playlist.playlist_clips.map(
                                (pc: any) => pc.clip.id
                              )
                            : [...selection]
                                .map((s) => s && indexToClip[s]?.clip?.id)
                                .filter((clipId): clipId is string => !!clipId);

                        // Store synchronously so onRemoveFromPlaylist can access immediately
                        menus.setPlaylistSelectedClipIds(selectedIds);

                        // Update preview for single selection
                        if (setPreviewClip && selectedIds[0]) {
                          setPreviewClip(clips.clipById[selectedIds[0]]);
                        }
                      }}
                    >
                      {(clipIndexObj) => {
                        const showTopTrackIcon =
                          session.flags?.['daily-mixes-enabled'] &&
                          clipIndexObj.playlistIndex === 0 &&
                          playlist.name === 'Echoes 2024' &&
                          playlist.user_handle === 'groovebot';

                        return session.flags?.['playlist-virtual'] ? (
                          'isLoadingIndicator' in clipIndexObj ? (
                            <GridListItemWrapper
                              style={{
                                position: 'absolute',
                                top: 0,
                                left: 0,
                                width: '100%',
                                height: `${virtualRows[virtualRows.length - 1]?.size || 0}px`,
                                transform: `translateY(${virtualRows[virtualRows.length - 1]?.start + virtualRows[virtualRows.length - 1]?.size || 0}px)`,
                              }}
                            >
                              <div
                                className='flex h-full w-full flex-row items-center justify-center'
                                ref={spinnerRef}
                                key={'-1'}
                                id={'-1'}
                              >
                                <SpinnerSVG />
                              </div>
                            </GridListItemWrapper>
                          ) : (
                            <GridListItemWrapper
                              style={{
                                position: 'absolute',
                                top: 0,
                                left: 0,
                                width: '100%',
                                height: `${virtualRows[clipIndexObj.playlistIndex - minVirtualIndex]?.size || 0}px`,
                                transform: `translateY(${virtualRows[clipIndexObj.playlistIndex - minVirtualIndex]?.start || 0}px)`,
                              }}
                            >
                              <PlaylistSongRow
                                key={clipIndexObj.id}
                                onClick={() => {}}
                                id={clipIndexObj.id}
                                rowKey={clipIndexObj.id}
                                index={clipIndexObj.playlistIndex}
                                showTopTrackIcon={showTopTrackIcon}
                                playlistId={playlist.id}
                                clip={clipIndexObj.clip.clip}
                                contextType={ContextType.Playlist}
                                contextId={playlist.id}
                                isDraggable={
                                  playlist.is_owned === null
                                    ? undefined
                                    : canEdit &&
                                      isTablet &&
                                      new Set(menus.selected).size <= 1
                                }
                                onPlay={() => {
                                  if (
                                    queueStore.isClipPlaying(
                                      clipIndexObj.clip.clip.id
                                    ) &&
                                    queueStore.isPlaylistCurrentContext(
                                      playlist.id
                                    ) &&
                                    !queueStore.currentPlayingSongIsRemoved
                                  ) {
                                    playbarState.togglePlay();
                                    return;
                                  }
                                  queueStore.setPlayContext({
                                    currentIndex: clipIndexObj.playlistIndex,
                                    clips: clipIndexes.map(
                                      (clipIndex: string) =>
                                        indexToClip[clipIndex].clip
                                    ),
                                    contextType: ContextType.Playlist,
                                    contextId: playlist.id,
                                  });
                                  playbarState.playClip(clipIndexObj.clip.clip);
                                  playlistStore.incrementPlaylistPlayCount(
                                    playlist.id,
                                    clipIndexObj.clip.clip.id
                                  );
                                }}
                              />
                            </GridListItemWrapper>
                          )
                        ) : (
                          <GridListItemWrapper>
                            <div
                              className='flex flex-col'
                              style={{ marginLeft: isMobile ? '-24px' : '0px' }}
                            >
                              <PlaylistSongRow
                                onClick={() => {}}
                                id={clipIndexObj.id}
                                rowKey={clipIndexObj.id}
                                index={clipIndexObj.playlistIndex}
                                showTopTrackIcon={showTopTrackIcon}
                                playlistId={playlist.id}
                                clip={clipIndexObj.clip.clip}
                                contextType={ContextType.Playlist}
                                contextId={playlist.id}
                                isDraggable={
                                  playlist.is_owned === null
                                    ? undefined
                                    : canEdit &&
                                      isTablet &&
                                      new Set(menus.selected).size <= 1
                                }
                                onPlay={() => {
                                  if (
                                    queueStore.isClipPlaying(
                                      clipIndexObj.clip.clip.id
                                    ) &&
                                    queueStore.isPlaylistCurrentContext(
                                      playlist.id
                                    ) &&
                                    !queueStore.currentPlayingSongIsRemoved
                                  ) {
                                    playbarState.togglePlay();
                                    return;
                                  }
                                  queueStore.setPlayContext({
                                    currentIndex: clipIndexObj.playlistIndex,
                                    clips: clipIndexes.map(
                                      (clipIndex: string) =>
                                        indexToClip[clipIndex].clip
                                    ),
                                    contextType: ContextType.Playlist,
                                    contextId: playlist.id,
                                  });
                                  playbarState.playClip(clipIndexObj.clip.clip);
                                  playlistStore.incrementPlaylistPlayCount(
                                    playlist.id,
                                    clipIndexObj.clip.clip.id
                                  );
                                }}
                              />
                            </div>
                          </GridListItemWrapper>
                        );
                      }}
                    </GridListWrapper>
                    {!session.flags?.['playlist-virtual'] &&
                      shouldInfiniteLoad &&
                      clipIndexes.length <
                        (clips.playlistById[playlist.id]?.num_total_results !==
                        undefined
                          ? clips.playlistById[playlist.id]?.num_total_results
                          : playlist.num_total_results || 0) && (
                        <div className='flex w-full items-center justify-center'>
                          <SpinnerSVG ref={spinnerRef} />
                        </div>
                      )}
                  </SortableContext>
                </DndContext>
              </PlaylistActionsProvider>
            )}
          </div>
        </div>
      </div>
      <Modal
        isOpen={isOpen}
        onClose={() => {
          setMetadataError(null);
          router.replace(pathname);
          onClose();
        }}
        data-qaid='modal-add-to-playlist'
        isCentered
      >
        <ModalOverlay />
        <ModalContent
          bg='var(--color-background-secondary)'
          color='var(--color-foreground-primary)'
        >
          <div className='flex items-center justify-between px-3'>
            <span className='pt-8 pl-4 font-serif text-3xl'>
              Update Playlist Details
            </span>
            <CloseButton onClick={onClose} />
          </div>
          <ModalBody>
            <TextInput
              placeholder='Playlist name'
              className='my-4 rounded-lg border border-border-primary bg-background-primary'
              maxLength={NAME_MAX_LENGTH}
              value={name || ''}
              onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
                setName(event.target.value)
              }
            />
            <TextareaV2
              placeholder='Playlist description'
              className='bg-background-primary text-foreground-primary'
              minRows={3}
              resize
              value={description || ''}
              onChange={(event) => setDescription(event.target.value)}
              maxLength={DESCRIPTION_MAX_LENGTH}
            />
            <div className='mt-2 flex flex-row justify-end'>
              <p className='text-sm text-gray-400'>
                <span
                  className={
                    descriptionLength > DESCRIPTION_MAX_LENGTH
                      ? 'text-accent-error-on-primary'
                      : 'text-foreground-inactive'
                  }
                >
                  {descriptionLength}
                </span>{' '}
                / {DESCRIPTION_MAX_LENGTH}
              </p>
            </div>
            <div className='mt-4'>
              <ImageUploader
                initialImageURL={initialImageURL || undefined}
                onImageChanged={(imageData: string | null) => {
                  setPlaylistImageDataURL(imageData);
                }}
                className='mx-auto mb-4'
              />
              <>
                <div className='mb-4 flex items-center gap-2'>
                  <TextInput
                    placeholder={'Prompt for an AI-generated image...'}
                    className='h-11 rounded-lg border border-border-primary'
                    value={editedImagePrompt}
                    maxLength={200}
                    onChange={(event) =>
                      setEditedImagePrompt(event.target.value)
                    }
                  />
                  <Button
                    variant={ButtonVariant.Primary}
                    size={ButtonSize.Small}
                    onClick={async () => {
                      setIsGenerating(true);
                      const { data } = await library.apiClient.POST(
                        '/api/gen/prompt_image/',
                        {
                          body: {
                            prompt: editedImagePrompt,
                          },
                        }
                      );
                      setIsGenerating(false);
                      if (data) {
                        setInitialImageURL((data as any).image_url);
                      }
                    }}
                    disabled={isGenerating || editedImagePrompt === ''}
                  >
                    Generate
                  </Button>
                </div>
                {isGenerating && (
                  <div className='flex flex-row items-center'>
                    <SpinnerSVG className='mr-2' />
                    <span>Generating AI image...</span>
                  </div>
                )}
              </>
            </div>
          </ModalBody>
          <ModalFooter>
            {metadataError && (
              <p
                className='mr-4 font-sans font-medium'
                style={{ color: 'var(--chakra-colors-red-300)' }}
              >
                {metadataError}
              </p>
            )}
            <Button
              variant={ButtonVariant.Primary}
              disabled={isSaveDisabled || loadingSetMetadata}
              icon={loadingSetMetadata && <SpinnerSVG />}
              onClick={async () => {
                const nameToSet = name;
                const descriptionToSet = description;
                if (!nameToSet) return;
                setLoadingSetMetadata(true);
                const { response, data, error } = await library.apiClient.POST(
                  '/api/playlist/set_metadata',
                  {
                    body: {
                      playlist_id: playlist.id,
                      name: nameToSet,
                      description: descriptionToSet || '',
                      image_url: playlistImageDataURL,
                    },
                  }
                );
                setLoadingSetMetadata(false);

                if (((data || error) as any)?.moderation_error_message) {
                  setMetadataError(
                    `Moderation error: ${
                      ((data || error) as any)?.moderation_error_message
                    }`
                  );
                } else if (response.status === 400) {
                  setMetadataError(
                    `Upload error: Your image might be too large to upload.`
                  );
                } else {
                  setMetadataError(null);
                  setPlaylistData((prevPlaylistData) => ({
                    ...prevPlaylistData,
                    name: nameToSet,
                    description: descriptionToSet || '',
                    image_url: playlistImageDataURL,
                  }));

                  // Update the global store so other components see the changes
                  if (clips.playlistById[playlist.id]) {
                    clips.playlistById[playlist.id] = {
                      ...clips.playlistById[playlist.id],
                      name: nameToSet,
                      description: descriptionToSet || '',
                      image_url: playlistImageDataURL,
                    };
                  }
                }
                router.replace(pathname);
                onClose();
              }}
            >
              Save
            </Button>
          </ModalFooter>
        </ModalContent>
      </Modal>
      <RegeneratePlaylistModal
        playlist={playlist}
        isOpen={isOpenRegeneratePlaylist}
        onClose={onCloseRegeneratePlaylist}
      />
    </main>
  );
});

export default PlaylistPageClient;
